Skip to content

fix: keep dashboard interactive on TTYs#23

Merged
flyingrobots merged 9 commits intomainfrom
fix/tui-no-color-interactive
Mar 18, 2026
Merged

fix: keep dashboard interactive on TTYs#23
flyingrobots merged 9 commits intomainfrom
fix/tui-no-color-interactive

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Mar 17, 2026

Summary

  • keep the vault dashboard interactive on real TTYs even when NO_COLOR=1
  • make launchDashboard() fall back to the static list for non-interactive CLI contexts instead of rendering a one-frame TUI
  • harden slow test timeouts and CLI integration time budgets so Node/Bun/Deno all stay green

Testing

  • npx eslint .
  • npm test
  • GIT_STUNTS_DOCKER=1 npx vitest run test/integration
  • bunx vitest run test/unit
  • GIT_STUNTS_DOCKER=1 bunx vitest run test/integration
  • deno run -A npm:vitest run test/unit
  • GIT_STUNTS_DOCKER=1 deno run -A npm:vitest run test/integration

Notes

  • I reproduced the dashboard bug locally: with NO_COLOR=1, the Bijou context downgraded to pipe mode and the dashboard rendered once before exiting.
  • Docker-based Bun verification became unreliable in this desktop session because the Docker daemon stalled during repeated image rebuilds, so I completed the Bun/Deno verification on the installed host runtimes as a fallback. The code paths are the same, but this PR should still rely on GitHub CI for the authoritative container-backed signal.

Summary by CodeRabbit

  • New Features

    • CLI now detects runtime and selects interactive, accessible, pipe, or static modes.
    • Dashboard launcher initializes with an explicit CLI context, respects injected terminal dimensions, and can emit static vault listings to a provided output.
  • Bug Fixes

    • Injected contexts without an explicit mode are treated as interactive to avoid unintended static fallbacks.
  • Tests

    • Added unit tests for CLI mode detection and launcher paths; increased several test timeouts.
  • Documentation

    • Changelog updated for dashboard launch context behavior.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds runtime-based CLI TUI mode detection and a CLI TUI context factory; updates the dashboard launcher to accept and normalize injected contexts and route between interactive TUI and non-interactive static output; and extends test timeouts and CLI-focused unit/integration tests.

Changes

Cohort / File(s) Summary
CLI Context & Mode Detection
bin/ui/context.js
Added detectCliTuiMode(runtime) to classify runtime as accessible/pipe/static/interactive, and createCliTuiContext(options) to build a Bijou CLI TUI context preserving NO_COLOR and deriving mode from runtime.
Dashboard Launch & DI
bin/ui/dashboard.js
Updated launchDashboard(cas, options) to accept injected ctx/runApp/output, added normalizeLaunchContext, switched to createCliTuiContext usage, changed createInitModel(ctx), and extended printStaticList(cas, output).
CLI Context Unit Tests
test/unit/cli/context.test.js
New unit tests for detectCliTuiMode covering accessible, TERM=dumb, NO_COLOR on TTY, stdout/stdin not TTY, and CI-on-TTY scenarios using mocked runtime.
Dashboard Launch Unit Tests
test/unit/cli/dashboard.launch.test.js, test/unit/cli/dashboard.launch.default.test.js
New tests mocking runtime, bijou-tui run, and CAS to verify interactive path uses runtime and non-interactive path writes tab-separated vault list to provided output.
Test Helpers & Mocks
test/unit/cli/_testContext.js, test/unit/cli/dashboard.test.js
Extended makeCtx(mode, runtime) to accept runtime and added mocked createCliTuiContext() in tests to simulate context creation.
Integration Test Timeouts
test/integration/round-trip.test.js, test/integration/vault-cli.test.js
Set Vitest global timeouts and increased CLI subprocess timeout from 30s to 90s.
Various Test Timeout Extensions
test/unit/domain/services/..., test/unit/facade/..., test/unit/vault/VaultService.test.js
Increased several slow-test timeout constants (15s–60s) across multiple unit test files to accommodate longer-running tests.
Changelog
CHANGELOG.md
Added entry documenting dashboard launch context normalization and CLI mode test coverage notes.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Client/CLI
    participant Detector as Mode Detector
    participant CtxFactory as Context Factory
    participant Dashboard as Dashboard Launcher
    participant TUI as Interactive TUI
    participant Static as Static Lister

    rect rgba(100,150,200,0.5)
    Note over CLI, TUI: Interactive path
    CLI->>Detector: detectCliTuiMode(runtime)
    Detector-->>CLI: mode = 'interactive' / 'accessible'
    CLI->>CtxFactory: createCliTuiContext(options)
    CtxFactory-->>CLI: ctx (mode=interactive)
    CLI->>Dashboard: launchDashboard(cas, {ctx, runApp})
    Dashboard->>TUI: runApp(ctx)
    TUI-->>Dashboard: renders interactive UI
    end

    rect rgba(200,150,100,0.5)
    Note over CLI, Static: Non-interactive path
    CLI->>Detector: detectCliTuiMode(runtime)
    Detector-->>CLI: mode = 'pipe' / 'static'
    CLI->>CtxFactory: createCliTuiContext(options)
    CtxFactory-->>CLI: ctx (mode=pipe/static)
    CLI->>Dashboard: launchDashboard(cas, {ctx, output})
    Dashboard->>Static: printStaticList(cas, output)
    Static->>Static: cas.listVault() -> format rows
    Static-->>CLI: write rows to output stream
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I sniffed the runtime, found its little mode,

Interactive hops or static lines in code,
Context stitched with care, NO_COLOR kept bright,
Dashboard listens whether TTY or pipe at night,
A jittery rabbit cheers the CLI's new light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objective: ensuring the dashboard remains interactive on TTYs even with NO_COLOR=1, which is the core bug fix in this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-no-color-interactive
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/unit/cli/context.test.js (1)

15-40: Add explicit tests for BIJOU_ACCESSIBLE and TERM=dumb branches.

Current coverage is good, but these two branches are part of mode precedence and worth locking with direct tests to prevent regressions.

Suggested test additions
 describe('detectCliTuiMode', () => {
+  it('uses accessible mode when BIJOU_ACCESSIBLE=1', () => {
+    const mode = detectCliTuiMode(makeRuntime({
+      env: { BIJOU_ACCESSIBLE: '1', TERM: 'xterm-256color' },
+    }));
+    expect(mode).toBe('accessible');
+  });
+
+  it('falls back to pipe when TERM is dumb', () => {
+    const mode = detectCliTuiMode(makeRuntime({
+      env: { TERM: 'dumb' },
+    }));
+    expect(mode).toBe('pipe');
+  });
+
   it('stays interactive on a TTY when NO_COLOR is set', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/cli/context.test.js` around lines 15 - 40, Add two explicit unit
tests for detectCliTuiMode: one that sets env BIJOU_ACCESSIBLE='1' (with TERM
present) and asserts the returned mode is the accessible mode (use
detectCliTuiMode with makeRuntime and expect mode === 'accessible' to lock that
branch), and another that sets TERM='dumb' (with a TTY stdout) and asserts the
returned mode is the static fallback (expect mode === 'static'); place them
alongside the existing tests so the BIJOU_ACCESSIBLE and TERM=dumb branches are
covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/ui/dashboard.js`:
- Around line 285-291: The JSDoc above printStaticList is incorrect—it's
describing an options object but the function signature is async function
printStaticList(cas, output = process.stdout). Update the JSDoc for
printStaticList to match the actual parameters by documenting the first
parameter (cas) and the second parameter (output, defaulting to process.stdout
and of type Pick<NodeJS.WriteStream,'write'> or a WriteStream), remove the
erroneous options block, and ensure types and brief descriptions reference the
exact symbol printStaticList and its parameters.

---

Nitpick comments:
In `@test/unit/cli/context.test.js`:
- Around line 15-40: Add two explicit unit tests for detectCliTuiMode: one that
sets env BIJOU_ACCESSIBLE='1' (with TERM present) and asserts the returned mode
is the accessible mode (use detectCliTuiMode with makeRuntime and expect mode
=== 'accessible' to lock that branch), and another that sets TERM='dumb' (with a
TTY stdout) and asserts the returned mode is the static fallback (expect mode
=== 'static'); place them alongside the existing tests so the BIJOU_ACCESSIBLE
and TERM=dumb branches are covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0d224d0-e36b-4355-baf1-ba32305914fc

📥 Commits

Reviewing files that changed from the base of the PR and between bedba6a and 6509987.

📒 Files selected for processing (14)
  • bin/ui/context.js
  • bin/ui/dashboard.js
  • test/integration/round-trip.test.js
  • test/integration/vault-cli.test.js
  • test/unit/cli/context.test.js
  • test/unit/cli/dashboard.launch.test.js
  • test/unit/cli/dashboard.test.js
  • test/unit/domain/services/CasService.compression.test.js
  • test/unit/domain/services/CasService.empty-file.test.js
  • test/unit/domain/services/CasService.envelope.test.js
  • test/unit/domain/services/CasService.kdf.test.js
  • test/unit/domain/services/rotateVaultPassphrase.test.js
  • test/unit/facade/ContentAddressableStore.rotation.test.js
  • test/unit/vault/VaultService.test.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
bin/ui/dashboard.js (1)

298-307: Tighten options.ctx contract for mode to avoid silent fallback.

Line 306 depends on ctx.mode, but Line 299 documents ctx as BijouContext. If a caller passes a plain BijouContext (no mode), this path always goes static (undefined !== 'interactive'). Consider normalizing or explicitly typing the injected context shape.

Suggested hardening
 /**
  * `@param` {ContentAddressableStore} cas
  * `@param` {{
- *   ctx?: BijouContext,
+ *   ctx?: (BijouContext & {
+ *     mode?: 'accessible' | 'pipe' | 'static' | 'interactive'
+ *   }),
  *   runApp?: typeof run,
  *   output?: Pick<NodeJS.WriteStream, 'write'>,
  * }} [options]
  */
 export async function launchDashboard(cas, options = {}) {
   const ctx = options.ctx || createCliTuiContext();
-  if (ctx.mode !== 'interactive') {
+  const mode = ctx.mode ?? 'interactive';
+  if (mode !== 'interactive') {
     return printStaticList(cas, options.output);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ui/dashboard.js` around lines 298 - 307, The code relies on ctx.mode in
launchDashboard but accepts a plain BijouContext which may lack mode; update
launchDashboard to normalize/validate the injected context: if options.ctx is
provided but options.ctx.mode is undefined, either set ctx = { ...options.ctx,
mode: createCliTuiContext().mode } (or call createCliTuiContext() when mode is
missing) or explicitly throw a clear error so callers must pass a context with
mode. Locate the logic around launchDashboard, the options.ctx usage,
createCliTuiContext, and the branch that calls printStaticList and ensure
ctx.mode is always defined before comparing to 'interactive'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/ui/dashboard.js`:
- Around line 298-307: The code relies on ctx.mode in launchDashboard but
accepts a plain BijouContext which may lack mode; update launchDashboard to
normalize/validate the injected context: if options.ctx is provided but
options.ctx.mode is undefined, either set ctx = { ...options.ctx, mode:
createCliTuiContext().mode } (or call createCliTuiContext() when mode is
missing) or explicitly throw a clear error so callers must pass a context with
mode. Locate the logic around launchDashboard, the options.ctx usage,
createCliTuiContext, and the branch that calls printStaticList and ensure
ctx.mode is always defined before comparing to 'interactive'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9692f047-7627-4de5-9649-0eb0f2c5da64

📥 Commits

Reviewing files that changed from the base of the PR and between 6509987 and c1079c1.

📒 Files selected for processing (1)
  • bin/ui/dashboard.js

@flyingrobots
Copy link
Member Author

@coderabbitai Addressed in commits 3f8baf0 and e14eb84.

Changes made:

  • Normalized injected dashboard contexts without an explicit mode so launchDashboard() no longer falls back to static output when callers pass a plain BijouContext (bin/ui/dashboard.js:299-314).
  • Added direct tests for BIJOU_ACCESSIBLE=1 and TERM='dumb' mode precedence (test/unit/cli/context.test.js:15-30).
  • Added a regression test covering an injected context with no mode (test/unit/cli/dashboard.launch.test.js:25-35).

One note on the earlier prompt: TERM='dumb' is intentionally pipe, not static, in the current implementation (bin/ui/context.js:40-48), so the test locks that designed behavior rather than changing it.

Review please 🥕

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the relevant files and line ranges
echo "=== bin/ui/dashboard.js lines 285-330 ==="
sed -n '285,330p' bin/ui/dashboard.js

echo "=== bin/ui/context.js lines 30-60 ==="
sed -n '30,60p' bin/ui/context.js

echo "=== test/unit/cli/context.test.js lines 1-50 ==="
sed -n '1,50p' test/unit/cli/context.test.js

echo "=== test/unit/cli/dashboard.launch.test.js lines 1-60 ==="
sed -n '1,60p' test/unit/cli/dashboard.launch.test.js

Length of output: 5356


🏁 Script executed:

#!/bin/bash
echo "=== test/unit/cli/_testContext.js ==="
cat test/unit/cli/_testContext.js 2>/dev/null || echo "FILE NOT FOUND"

echo "=== test/unit/cli/context.test.js lines 50-60 ==="
sed -n '50,60p' test/unit/cli/context.test.js

echo "=== bin/ui/context.js lines 55-90 ==="
sed -n '55,90p' bin/ui/context.js

Length of output: 1458


@flyingrobots: Thanks for the detailed summary — I've read through both commits. Here's the verdict:


✅ What's solid

  • Normalization in launchDashboard (dashboard.js:308): the mode ?? detectCliTuiMode(options.ctx.runtime) fallback is clean and correctly restores interactive behaviour for injected plain contexts.
  • detectCliTuiMode decision tree (context.js:41–53): the priority order (accessible → dumb → no-TTY → CI → interactive) is well-reasoned and now fully covered by the five tests in context.test.js.
  • TERM=dumbpipe design note: locking the behaviour in a test rather than silently accepting an alternative is the right call. Appreciated.

⚠️ Two mild residual concerns

1. Regression test relies implicitly on createTestContext's runtime defaults (dashboard.launch.test.js:25–35)

makeCtx delegates to createTestContext({ mode, noColor: true }), which comes from an external adapter. After delete ctx.mode, the normalization path calls detectCliTuiMode(options.ctx.runtime) — and the test's assertion only holds if that adapter's runtime reports stdoutIsTTY: true, stdinIsTTY: true, no CI, and no TERM=dumb. If createTestContext ever adopts headless/pipe defaults (a reasonable choice for a test helper), the test would silently invert.

A more robust form would be to inject an explicit runtime, mirroring the style already used in context.test.js:

it('treats an injected context without mode as interactive', async () => {
  const cas = mockCas();
  const runtime = makeRuntime({ env: { TERM: 'xterm-256color' } });
  const ctx = { ...makeCtx('interactive'), runtime };
  delete ctx.mode;

  await launchDashboard(cas, { ctx, runApp: runMock });

  expect(runMock).toHaveBeenCalledTimes(1);
  expect(cas.listVault).not.toHaveBeenCalled();
});

This makes the test's intent explicit and independent of the adapter.


2. No null-guard on runtime in the normalization path (dashboard.js:308)

mode: options.ctx.mode ?? detectCliTuiMode(options.ctx.runtime),

If a caller passes a plain object with neither mode nor runtime, detectCliTuiMode(undefined) will throw TypeError: Cannot read properties of undefined (reading 'env'). Low risk in production (real Bijou contexts always carry a runtime port), but a defensive guard keeps the error message meaningful:

mode: options.ctx.mode ?? (options.ctx.runtime
  ? detectCliTuiMode(options.ctx.runtime)
  : 'interactive'),

Or simply document the invariant in the JSDoc (@param note: "ctx.runtime is required when ctx.mode is absent").


Neither concern is a blocker — the core fix is correct and the test coverage is meaningfully improved. Happy to approve once item 1 is addressed (item 2 can go in a follow-up if preferred). 🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
CHANGELOG.md (1)

24-24: Changelog description doesn't fully match the actual code behavior.

The changelog states contexts without an explicit mode are treated "as interactive," but the implementation calls detectCliTuiMode() which returns one of four modes based on environment conditions:

  • 'accessible' when BIJOU_ACCESSIBLE=1
  • 'pipe' when TERM=dumb or non-TTY
  • 'static' when CI env var is set
  • 'interactive' as the default fallback

Consider rewording to something like: "now properly detects the mode for injected Bijou contexts without an explicit mode, avoiding an incorrect static fallback on real TTYs."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 24, Update the changelog sentence to accurately reflect
the behavior of launchDashboard(): it doesn't unconditionally treat contexts
without mode as interactive but defers to detectCliTuiMode(), which can return
'accessible' (BIJOU_ACCESSIBLE=1), 'pipe' (TERM=dumb or non‑TTY), 'static' (CI
set), or 'interactive' as the fallback; rephrase to something like "now properly
detects the mode for injected Bijou contexts without an explicit mode (via
detectCliTuiMode()), avoiding an incorrect static fallback on real TTYs" and
mention the CLI tests lock BIJOU_ACCESSIBLE and TERM=dumb branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@CHANGELOG.md`:
- Line 24: Update the changelog sentence to accurately reflect the behavior of
launchDashboard(): it doesn't unconditionally treat contexts without mode as
interactive but defers to detectCliTuiMode(), which can return 'accessible'
(BIJOU_ACCESSIBLE=1), 'pipe' (TERM=dumb or non‑TTY), 'static' (CI set), or
'interactive' as the fallback; rephrase to something like "now properly detects
the mode for injected Bijou contexts without an explicit mode (via
detectCliTuiMode()), avoiding an incorrect static fallback on real TTYs" and
mention the CLI tests lock BIJOU_ACCESSIBLE and TERM=dumb branches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3aa0138e-9a17-4cb1-8d2e-1088cf0038ac

📥 Commits

Reviewing files that changed from the base of the PR and between c1079c1 and e14eb84.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • bin/ui/dashboard.js
  • test/unit/cli/context.test.js
  • test/unit/cli/dashboard.launch.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/unit/cli/context.test.js
  • test/unit/cli/dashboard.launch.test.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
bin/ui/dashboard.js (1)

295-310: Consider adding a defensive guard for undefined runtime.

If an injected context lacks both mode and runtime, line 308 will call detectCliTuiMode(undefined), which may throw or produce unexpected results. While createCliTuiContext() guarantees a runtime (per context snippet 1), manually injected contexts in tests or future code may not.

This was flagged as a non-blocking concern in previous review comments. A minimal guard would make the contract explicit.

🛡️ Proposed defensive guard
 function normalizeLaunchContext(ctx) {
   const candidate = /** `@type` {BijouContext & { mode?: import('@flyingrobots/bijou').OutputMode }} */ (ctx);
   if (candidate.mode) {
     return candidate;
   }
+  if (!candidate.runtime) {
+    throw new TypeError('ctx.runtime is required when ctx.mode is absent');
+  }
   return {
     ...candidate,
     mode: detectCliTuiMode(candidate.runtime),
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ui/dashboard.js` around lines 295 - 310, normalizeLaunchContext currently
assumes ctx.runtime exists and calls detectCliTuiMode(candidate.runtime), which
can misbehave if a context is injected without runtime; add a defensive guard in
normalizeLaunchContext to supply a safe default runtime (or undefined-safe
value) before calling detectCliTuiMode, e.g. check candidate.runtime and if
missing provide the same default runtime used by createCliTuiContext or return
candidate with a sensible mode (fallback) instead; update references in
normalizeLaunchContext and the call to detectCliTuiMode so tests and manual
injections don’t pass undefined.
test/unit/cli/dashboard.launch.default.test.js (1)

6-12: Consider extending mockCas to align with the actual CAS interface.

The mock only stubs three methods. If future tests exercise additional CAS methods, they'll fail silently with undefined. For a test helper, this is acceptable, but documenting the minimal interface or using a more complete stub could improve maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/cli/dashboard.launch.default.test.js` around lines 6 - 12, The
mockCas helper currently only implements listVault, getVaultMetadata, and
readManifest which can cause future tests to fail if other CAS methods are
called; update mockCas to implement the fuller CAS interface used across the
codebase (at minimum add no-op/vi.fn().mockResolvedValue or mockRejectedValue
stubs for commonly used methods such as writeObject, readObject, deleteObject,
headObject, listObjects, and any metadata/update methods), keep the existing
entries parameter behavior, and document in the mockCas JSDoc comment which
methods are intentionally stubbed so callers know the minimal supported surface
(refer to mockCas, listVault, getVaultMetadata, readManifest when locating where
to add the extra stubs).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/ui/dashboard.js`:
- Around line 295-310: normalizeLaunchContext currently assumes ctx.runtime
exists and calls detectCliTuiMode(candidate.runtime), which can misbehave if a
context is injected without runtime; add a defensive guard in
normalizeLaunchContext to supply a safe default runtime (or undefined-safe
value) before calling detectCliTuiMode, e.g. check candidate.runtime and if
missing provide the same default runtime used by createCliTuiContext or return
candidate with a sensible mode (fallback) instead; update references in
normalizeLaunchContext and the call to detectCliTuiMode so tests and manual
injections don’t pass undefined.

In `@test/unit/cli/dashboard.launch.default.test.js`:
- Around line 6-12: The mockCas helper currently only implements listVault,
getVaultMetadata, and readManifest which can cause future tests to fail if other
CAS methods are called; update mockCas to implement the fuller CAS interface
used across the codebase (at minimum add no-op/vi.fn().mockResolvedValue or
mockRejectedValue stubs for commonly used methods such as writeObject,
readObject, deleteObject, headObject, listObjects, and any metadata/update
methods), keep the existing entries parameter behavior, and document in the
mockCas JSDoc comment which methods are intentionally stubbed so callers know
the minimal supported surface (refer to mockCas, listVault, getVaultMetadata,
readManifest when locating where to add the extra stubs).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98aea53a-fc5d-420f-9fd1-442cbd1785ca

📥 Commits

Reviewing files that changed from the base of the PR and between e14eb84 and d8d6fae.

📒 Files selected for processing (5)
  • bin/ui/dashboard.js
  • test/unit/cli/_testContext.js
  • test/unit/cli/context.test.js
  • test/unit/cli/dashboard.launch.default.test.js
  • test/unit/cli/dashboard.launch.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/unit/cli/context.test.js
  • test/unit/cli/dashboard.launch.test.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/ui/dashboard.js`:
- Around line 85-89: The createInitModel function reads ctx.runtime.columns and
ctx.runtime.rows but normalizeLaunchContext only ensures runtime when mode is
missing, so add a defensive check in createInitModel: if ctx.runtime is falsy,
either assign a default runtime object (e.g., {columns: 80, rows: 24}) or throw
a clear error; update createInitModel (and/or normalizeLaunchContext) to
guarantee ctx.runtime exists before accessing properties so calls that pass a
context with mode set but no runtime don't cause a TypeError.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e9be0b0-ed65-4957-91a3-750768f6f177

📥 Commits

Reviewing files that changed from the base of the PR and between d8d6fae and 05a1362.

📒 Files selected for processing (2)
  • bin/ui/dashboard.js
  • test/unit/cli/dashboard.launch.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/unit/cli/dashboard.launch.test.js

Comment on lines +85 to +89
function createInitModel(ctx) {
return {
status: 'loading',
columns: process.stdout.columns ?? 80,
rows: process.stdout.rows ?? 24,
columns: ctx.runtime.columns ?? 80,
rows: ctx.runtime.rows ?? 24,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential TypeError if ctx has mode but no runtime.

normalizeLaunchContext only validates that runtime exists when mode is absent (lines 306-308). If a caller passes a context with mode set but runtime undefined, this function will throw TypeError: Cannot read properties of undefined (reading 'columns').

Consider adding a defensive check or documenting the invariant that ctx.runtime is always required.

Suggested defensive check
 function createInitModel(ctx) {
+  const runtime = ctx.runtime ?? {};
   return {
     status: 'loading',
-    columns: ctx.runtime.columns ?? 80,
-    rows: ctx.runtime.rows ?? 24,
+    columns: runtime.columns ?? 80,
+    rows: runtime.rows ?? 24,
     entries: [],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ui/dashboard.js` around lines 85 - 89, The createInitModel function reads
ctx.runtime.columns and ctx.runtime.rows but normalizeLaunchContext only ensures
runtime when mode is missing, so add a defensive check in createInitModel: if
ctx.runtime is falsy, either assign a default runtime object (e.g., {columns:
80, rows: 24}) or throw a clear error; update createInitModel (and/or
normalizeLaunchContext) to guarantee ctx.runtime exists before accessing
properties so calls that pass a context with mode set but no runtime don't cause
a TypeError.

@flyingrobots
Copy link
Member Author

@coderabbitai Fresh review requested. A carrot is promised for your troubles 🥕

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

@flyingrobots: A carrot is always appreciated! 🥕 Triggering a fresh review now — I'll take a close look at the latest commits and report back shortly.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/unit/cli/dashboard.launch.test.js (1)

38-53: Test implicitly depends on createTestContext runtime handling.

The test passes env as an object (line 42), but detectCliTuiMode calls runtime.env() as a function. This works because createTestContext presumably wraps the env object. If the test helper's behavior changes, this test could break silently.

Consider making the runtime explicit to decouple from helper internals:

Suggested explicit runtime
   it('treats an injected context without mode as interactive', async () => {
     const cas = mockCas();
+    const envMap = { TERM: 'xterm-256color' };
     const ctx = {
       ...makeCtx('interactive', {
-        env: { TERM: 'xterm-256color' },
+        env: (key) => envMap[key],
         stdoutIsTTY: true,
         stdinIsTTY: true,
       }),
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/cli/dashboard.launch.test.js` around lines 38 - 53, The test relies
on createTestContext's wrapping of env as a function, so update the injected
context passed to launchDashboard to provide the runtime shape detectCliTuiMode
expects: replace the plain env object with a runtime-like API (e.g., provide env
as a function or a runtime object with env() returning the env), and ensure
stdoutIsTTY/stdinIsTTY are set on that runtime or on the ctx the code reads;
modify the call site created by makeCtx/ctx so detectCliTuiMode sees
runtime.env() explicitly, avoiding implicit dependence on createTestContext
internals when calling launchDashboard and makeCtx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/unit/cli/dashboard.launch.test.js`:
- Around line 38-53: The test relies on createTestContext's wrapping of env as a
function, so update the injected context passed to launchDashboard to provide
the runtime shape detectCliTuiMode expects: replace the plain env object with a
runtime-like API (e.g., provide env as a function or a runtime object with env()
returning the env), and ensure stdoutIsTTY/stdinIsTTY are set on that runtime or
on the ctx the code reads; modify the call site created by makeCtx/ctx so
detectCliTuiMode sees runtime.env() explicitly, avoiding implicit dependence on
createTestContext internals when calling launchDashboard and makeCtx.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9660cfdd-34c0-4e43-b5a0-e62df31f71a2

📥 Commits

Reviewing files that changed from the base of the PR and between d8d6fae and 05a1362.

📒 Files selected for processing (2)
  • bin/ui/dashboard.js
  • test/unit/cli/dashboard.launch.test.js

@flyingrobots flyingrobots merged commit 3b0f3b2 into main Mar 18, 2026
6 checks passed
@flyingrobots flyingrobots deleted the fix/tui-no-color-interactive branch March 18, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant